#!/usr/bin/env python3
# _*_ coding:utf-8 _*_

import functools
from types import FunctionType
from django.conf.urls import url
from django.shortcuts import HttpResponse, reverse, render, redirect
from django.utils.safestring import mark_safe
from django.http import QueryDict
from django.db.models import Q
from django.db.models import ForeignKey, ManyToManyField

from Arc.utils.GetData import getpage
from Arc.utils import BaseForm


def get_choice_text(title, field):
    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return title
        method = 'get_%s_display' % field
        return mark_safe('<span class="label label-warning">%s</span>' % getattr(obj, method)())

    return inner


def get_m2m_text(title, field):
    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return title
        queryset = getattr(obj, field).all()
        text_list = ['''<span class="label">%s</span>''' % str(row) for row in queryset]

        return mark_safe(' '.join(text_list))

    return inner


class SearchGroupRow(object):

    def __init__(self, title, queryset_or_tuple, search_option, query_dict):
        """

        :param title: 组合搜索的列名称
        :param queryset_or_tuple: 组合搜索获取到的数据
        :param search_option: SearchOption，组合搜索配置实例
        :param query_dict: request.GET
        """
        self.title = title
        self.queryset_or_tuple = queryset_or_tuple
        self.search_option = search_option
        self.query_dict = query_dict

    def __iter__(self):
        yield '<div class="row">'
        yield '<div class="whole screen">%s</div>' % self.title
        yield '<div class="others screen">'
        total_quert_dict = self.query_dict.copy()
        total_quert_dict._mutable = True
        origin_value_list = self.query_dict.getlist(self.search_option.field)
        if not origin_value_list:
            yield '<a href="?%s" class="btn btn-outline btn-info active">全部</a>' % total_quert_dict.urlencode()
        else:
            total_quert_dict.pop(self.search_option.field)
            yield '<a href="?%s" class="btn btn-outline btn-info">全部</a>' % total_quert_dict.urlencode()
        for item in self.queryset_or_tuple:
            text = self.search_option.get_text(item)
            value = str(self.search_option.get_value(item))
            query_dict = self.query_dict.copy()
            query_dict._mutable = True
            if not self.search_option.is_multi:
                query_dict[self.search_option.field] = value
                if value in origin_value_list:
                    query_dict.pop(self.search_option.field)
                    yield '<a href="?%s" class="btn btn-outline btn-info active">%s</a>' % (
                        query_dict.urlencode(), text)
                else:
                    yield '<a href="?%s" class="btn btn-outline btn-info ">%s</a>' % (
                        query_dict.urlencode(), text)
            else:
                multi_value_list = query_dict.getlist(self.search_option.field)
                if value in multi_value_list:
                    multi_value_list.remove(value)
                    query_dict.setlist(self.search_option.field, multi_value_list)
                    yield '<a href="?%s" class="btn btn-outline btn-info active">%s</a>' % (
                        query_dict.urlencode(), text)
                else:
                    multi_value_list.append(value)
                    query_dict.setlist(self.search_option.field, multi_value_list)
                    yield '<a href="?%s" class="btn btn-outline btn-info ">%s</a>' % (
                        query_dict.urlencode(), text)
        yield '</div>'
        yield '</div>'


class SearchOption(object):
    def __init__(self, field, is_multi=False, condition=None, text_func=None, value_func=None):
        self.field = field
        self.is_multi = is_multi
        if not condition:
            condition = {}
        self.condition = condition
        self.text_func = text_func
        self.value_func = value_func
        self.is_choice = False

    def get_condition(self, *args, **kwargs):
        return self.condition

    def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
        field_obj = model_class._meta.get_field(self.field)
        # print(field_obj.verbose_name)
        title = field_obj.verbose_name

        if isinstance(field_obj, ForeignKey) or isinstance(field_obj, ManyToManyField):
            condition = self.get_condition(*args, **kwargs)
            queryset_or_tuple = field_obj.remote_field.model.objects.filter(**condition)
            return SearchGroupRow(title, queryset_or_tuple, self, request.GET)
        else:
            self.is_choice = True
            return SearchGroupRow(title, field_obj.choices, self, request.GET)

    def get_text(self, field_obj):
        if self.text_func:
            return self.text_func(field_obj)
        if self.is_choice:
            return field_obj[1]

        return str(field_obj)

    def get_value(self, field_object):
        if self.value_func:
            return self.value_func(field_object)

        if self.is_choice:
            return field_object[0]

        return field_object.pk


class ArcHandler(object):
    def __init__(self, site, model_class, prev):
        self.site = site
        self.model_class = model_class
        self.prev = prev
        self.request = None

    per_page_count = 10
    display_list = []
    has_add_btn = True
    model_form_class = None
    search_list = []
    search_group = []
    order_list = []
    action_list = []

    def mutli_del(self, request, *args, **kwargs):
        pk_list = request.POST.getlist('pk')
        self.queryset_obj.filter(id__in=pk_list).delete()

    def mutli_init(self, request, *args, **kwargs):
        pass

    mutli_del.text = '批量删除'
    mutli_init.text = '批量初始化'

    def get_order_list(self):
        return self.order_list or ['-id', ]

    def get_search_list(self):
        return self.search_list

    def get_search_group(self):
        return self.search_group

    def get_action_list(self):
        return self.action_list

    def get_search_group_condition(self, request, *args, **kwargs):
        condition = {}
        for option in self.get_search_group():
            if option.is_multi:
                value_list = request.GET.getlist(option.field)
                if not value_list:
                    continue
                condition['%s__in' % option.field] = value_list
            else:
                value = request.GET.get(option.field)
                if not value:
                    continue
                condition[option.field] = value
        return condition

    def get_url_name(self, param):
        app_lable, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            return '%s_%s_%s_%s' % (app_lable, model_name, self.prev, param)
        return '%s_%s_%s' % (app_lable, model_name, param)

    def reverse_url(self, urlname, *args, **kwargs):
        name = "%s:%s" % (self.site.namespace, urlname)
        base_url = reverse(name, args=args, kwargs=kwargs)
        if not self.request.GET:
            str_url = base_url
        else:
            param = self.request.GET.urlencode()
            new_query_dict = QueryDict(mutable=True)
            new_query_dict['_filter'] = param
            str_url = "%s?%s" % (base_url, new_query_dict.urlencode())
        return str_url

    def get_list_url(self, *args, **kwargs):
        name = '%s:%s' % (self.site.namespace, self.get_url_name('list'))
        base_url = reverse(name, args=args, kwargs=kwargs)
        param = self.request.GET.get('_filter')
        if not param:
            return base_url
        return "%s?%s" % (base_url, param)

    def add_btn(self, request, *args, **kwargs):
        if self.has_add_btn:
            return '<a href="%s" class="btn btn-primary "><i class="fa fa-plus-square-o"></i>&nbsp;添加</a>' % self.reverse_url(
                self.get_url_name('add'))
        return None

    def display_checkbox(self, obj=None, is_header=None, *args, **kwargs):
        """
        :param obj:
        :param is_header:
        :return:
        """
        if is_header:
            return mark_safe('<div class="i-checks"><input type="checkbox" id="check_all"><i></i></div>')
        return mark_safe(
            '<div class="i-checks"><input type="checkbox" value="%s" name="pk"> <i></i></div>' % obj.pk)

    def action_btn(self, obj=None, *args, **kwargs):
        info = ''
        return info

    def display_edit(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return "操作"
        return mark_safe('''
        <a href="%s" class=" btn btn-primary btn-sm get_user_info"> <i class="fa fa-edit"></i>修改 </a>
        ''' % self.reverse_url(self.get_url_name('edit'), pk=obj.pk)
                         )

    def display_del(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return "操作"
        return mark_safe('''
        <a href="#" class=" btn btn-danger btn-sm a-del" id="%s" del-url="%s"> <i class="fa fa-times"></i>删除 </a>
        ''' % (
            obj.pk,
            reverse(self.get_url_name('del'), kwargs={'pk': obj.pk}),
        )
                         )

    def display_edit_del(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return "操作"
        info = self.action_btn(obj=obj, *args, **kwargs)
        return mark_safe(
            '''
            <a href="%s" class=" btn btn-primary btn-sm "> <i class="fa fa-edit"></i>修改 </a>
            <a href="#" class=" btn btn-danger btn-sm a-del" id="%s" del-url="%s"> <i class="fa fa-times"></i>删除 </a>
            %s
            ''' % (
                self.reverse_url(self.get_url_name('edit'), pk=obj.pk),
                obj.pk,
                reverse('%s:%s' % (self.site.namespace, self.get_url_name('del')), kwargs={'pk': obj.pk}),
                info
            )
        )

    def get_display_list(self, request, *args, **kwargs):
        value = []
        if self.display_list:
            value.extend(self.display_list)
            value.append(type(self).display_edit_del)
        return value

    def get_modelform_class(self, pk, is_add=False, *args, **kwargs):
        if self.model_form_class:
            return self.model_form_class
        else:
            class DynamicModelForm(BaseForm.StarkModelForm):
                class Meta:
                    model = self.model_class
                    fields = "__all__"

            return DynamicModelForm

    def formsave(self, request, forms, is_update, *args, **kwargs):
        '''
        在使用Form保存数据之前预留钩子方法
        :param request:
        :param forms:
        :param is_update:
        :param args:
        :param kwargs:
        :return:
        '''
        forms.save()
        return False

    @property
    def queryset_obj(self):
        return self.model_class.objects

    def get_queryset(self, request, *args, **kwargs):
        '''
        过滤指定数据的钩子函数
        :param request:
        :param args:
        :param kwargs:
        :return:
        '''
        return self.model_class.objects

    def list_view(self, request, *args, **kwargs):
        error = None
        action_list = self.get_action_list()
        action_dict = {func.__name__: func.text for func in action_list}
        if request.method == 'POST':
            func_name = request.POST.get('action')
            if not func_name:
                error = ''' <div class="alert alert-danger alert-dismissable"> <button aria-hidden="true" 
            data-dismiss="alert" class="close" type="button">× </button> 没有选择批量操作！！ . </div>
            '''
            else:
                if func_name in action_dict:
                    action_response = getattr(self, func_name)(request, *args, **kwargs)
                    if action_response:
                        return action_response

        order_list = self.get_order_list()
        search_list = self.get_search_list()
        search_group = self.get_search_group()
        search_group_condition = self.get_search_group_condition(request, *args, **kwargs)
        search_group_row = []
        if search_group:
            for option in search_group:
                row = option.get_queryset_or_tuple(self.model_class, request, *args, **kwargs)
                search_group_row.append(row)
        # Q 构造复杂的查询条件
        conn = Q()
        conn.connector = 'OR'
        if search_list:
            search_value = request.GET.get('q', '')
            if search_value:
                for field in search_list:
                    item = '%s__icontains' % field
                    conn.children.append((item, search_value))
        else:
            search_value = None
        # 过滤数据
        get_queryset = self.get_queryset(request, *args, **kwargs)

        queryset = get_queryset.filter(conn).filter(**search_group_condition).order_by(*order_list)

        pager, data_list = getpage(request, queryset, per_page=self.per_page_count, pager_page_count=7)
        display_list = self.get_display_list(request, *args, **kwargs)
        table_head = []
        if display_list:
            for key_or_func in display_list:
                if isinstance(key_or_func, FunctionType):
                    verbose_name = key_or_func(self, obj=None, is_header=True)
                else:
                    verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name

                table_head.append(verbose_name)
        else:
            table_head.append(self.model_class._meta.model_name)

        table_body = []
        for row in data_list:
            tr_list = []
            if display_list:
                for key_or_func in display_list:
                    if isinstance(key_or_func, FunctionType):
                        tr_list.append(key_or_func(self, row, False, *args, **kwargs))
                    else:
                        tr_list.append(getattr(row, key_or_func))
            else:
                tr_list.append(row)
            table_body.append(tr_list)

        return render(request, 'arc/list.html',
                      {
                          'table_head': table_head,
                          'table_body': table_body,
                          'pager': pager,
                          'data_list': data_list,
                          'add_btn': self.add_btn(request, *args, **kwargs),
                          'search_list': search_list,
                          'search_value': search_value,
                          'action_dict': action_dict,
                          'error': error,
                          'search_group_row': search_group_row,
                      })

    def add_view(self, request, *args, **kwargs):
        model_form_class = self.get_modelform_class(None, True, *args, **kwargs)
        if request.method == 'GET':
            forms = model_form_class()
            return render(request, 'arc/form_temp.html', {'forms': forms})
        forms = model_form_class(data=request.POST)
        if forms.is_valid():
            response = self.formsave(request, forms, False, *args, **kwargs)
            return response or redirect(self.get_list_url(*args, **kwargs))
        return render(request, 'arc/form_temp.html', {'forms': forms})

    def del_view(self, request, pk, *args, **kwargs):
        if request.method == 'POST':
            if request.POST.get('pk') == pk and request.POST.get('csrfmiddlewaretoken'):
                self.queryset_obj.filter(pk=pk).delete()
                return HttpResponse('200')
        else:
            return render(request, '403.html')

    def edit_view(self, request, pk, *args, **kwargs):
        obj = self.queryset_obj.filter(pk=pk).first()
        if not obj:
            return render(request, '404.html', {'error': '要修改的数据不存在！'})

        model_form_class = self.get_modelform_class(pk, *args, **kwargs)
        if request.method == 'GET':
            forms = model_form_class(instance=obj)
            return render(request, 'arc/form_temp.html', {'forms': forms})
        forms = model_form_class(data=request.POST, instance=obj)
        if forms.is_valid():
            response = self.formsave(request, forms, True, *args, **kwargs)
            return response or redirect(self.get_list_url(*args, **kwargs))
        return render(request, 'arc/form_temp.html', {'forms': forms})

    def wrapper(self, func):
        @functools.wraps(func)
        def inner(request, *args, **kwargs):
            self.request = request
            return func(request, *args, **kwargs)

        return inner

    def extra_urls(self):
        return []

    def get_urls(self):
        patterns = [
            url(r'^list/$', self.wrapper(self.list_view), name=self.get_url_name('list')),
            url(r'^add/$', self.wrapper(self.add_view), name=self.get_url_name('add')),
            url(r'^del/(?P<pk>\d+)/$', self.wrapper(self.del_view), name=self.get_url_name('del')),
            url(r'^edit/(?P<pk>\d+)/$', self.wrapper(self.edit_view), name=self.get_url_name('edit')),
        ]
        patterns.extend(self.extra_urls())
        return patterns


class ArcSite(object):
    def __init__(self):
        self._registry = []
        self.app_name = 'Arc'
        self.namespace = self.app_name

    def register(self, model_class, handler_class=None, prev=None):
        if not handler_class:
            handler_class = ArcHandler

        self._registry.append(
            {
                'model_class': model_class,
                'handler_class': handler_class(self, model_class, prev),
                'prev': prev,
            }
        )

    def get_urls(self):
        patterns = []
        for item in self._registry:
            model_class = item['model_class']
            handler = item['handler_class']
            prev = item['prev']
            app_label, model_name = model_class._meta.app_label, model_class._meta.model_name

            if prev:
                patterns.append(url(r'^%s/%s/%s/' % (app_label, model_name, prev), (handler.get_urls(), None, None)))
            else:
                patterns.append(url(r'%s/%s/' % (app_label, model_name), (handler.get_urls(), None, None)))

        return patterns

    @property
    def urls(self):
        return self.get_urls(), self.app_name, self.namespace


site = ArcSite()
